package com.axend.aerosense.common.connect.update;

import com.axend.aerosense.base.storage.MmkvHelper;
import com.axend.aerosense.base.utils.ByteUtils;
import com.axend.aerosense.base.utils.LogUtils;
import com.axend.aerosense.base.utils.Values;
import com.axend.aerosense.common.config.Gobal;
import com.axend.aerosense.common.connect.update.firmware.CommandList;
import com.axend.aerosense.common.connect.tcp.RadarProvide;
import com.axend.aerosense.common.connect.tcp.TcpClient;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 用于固件升级
 * @author ChenHongWei
 * @date :2020/11/27 9:15
 */

public class FirmwareUpdate {

    private static final int DATA_PACK_SIZE = 220;  //数据包大小（字节）
    private static final String FILE_TYPE = ".bin";  //升级文件类型

    public static final int FILE_NOT_FOUND = 101;  //文件不存在
    public static final int FILE_READ_FAILED = 102;  //文件读取失败
    public static final int STATUS_READY = 201;  //一切准备就绪
    public static final int STATUS_UPDATE_SUCCESS = 202;  //烧录成功！
    public static final int STATUS_DEVICE_UNCONNECTED = 203;  //设备未连接
    public static final int STATUS_CONNECT_EXCEPTION = 204;  //Tcp连接异常
    public static final int STATUS_UPDATE_EXCEPTION = 205;  //固件升级异常

    private UpdateStatusCallback callback;
    private TcpClient tcpClient;
    private RadarProvide radarProvide;
    private File file;
    private int fileLength;  //文件长度（字节）
    private long countSendLength;  //已发文件长度（字节）
    private int sendCount;  //发送数据包的次数
    private FileInputStream inputStream;
    private int ackLoop = 0;  //ack循环的次数
    private int fileSendCount = 0;  //需要发送文件次数

    private boolean isFileReady = false;  //文件获取状态是否就绪
    private boolean isTcpConnectReady = false;  //文件获取状态是否就绪
    private boolean isRun = true;

    private byte[] currentSendData;  //当前发送的数据包
    private final int resendCount = 5;  //重发次数
    private int currentResendCount = 0;  //当前重发次数
    private long currentTime;  //当前时间（秒）
    private long endTime = 20;  //等待时间（秒）
    private Thread checkThread;

    //初始化
    public void init() {
        //加载升级文件
        loadUpdateFile();
    }

    private void initTcp(){
        //初始化TCP客户端
        tcpClient = TcpClient.getInstance();
        tcpClient.setCallback(getCallback());
        tcpClient.createConnect();
        radarProvide = new RadarProvide(tcpClient);
    }

    //TCP客户端回调
    private TcpClient.Callback getCallback(){
        return new TcpClient.Callback() {
            @Override
            public void connected() {
                isTcpConnectReady = true;
                if (isFileReady){
                    callbackStatus(STATUS_READY);
                }
            }

            @Override
            public void failed(int state) {
                callbackStatus(STATUS_CONNECT_EXCEPTION);
                isRun = false;
            }

            @Override
            public void readData(String tag, byte[] data) {
                LogUtils.d(FirmwareUpdate.this, "tag:" + tag + ", " + hex2String(data) + ", ackLoop:" + ackLoop + ", fileSendCount:" + fileSendCount);

                //收到雷达回复消息，重置时间
                currentTime = 0;

                if (isSuccess(data)){
                    switch (tag){
                        case Values.START_FIRMWARE_UPDATE:
                            //发送开始升级命令，雷达恢复准备就绪，下一步发送升级文件
                            LogUtils.d(FirmwareUpdate.this, "发送开始升级命令");
                            sendCommand();
                            break;

                        case Values.SEND_FIRMWARE_DATA:
                            ackLoop++;
                            sendCommand();
                            break;

                        case Values.UPDATE_SUCCESS:
                            //升级成功，设置雷达IP地址与端口（此处延时比较久，雷达需要重启，等待重启完成才能接收到命令）
                            new Thread(()->{
                                try {
                                    Thread.sleep(3000);
                                    LogUtils.d(FirmwareUpdate.this, "设置服务器IP和端口命令");
                                    radarProvide.setDeviceIPAndPort();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }).start();
                            break;

                        case Values.SET_RADAR_IP_PORT:
                            //设置雷达IP地址与端口成功
                            new Thread(()->{
                                try {
                                    Thread.sleep(1000);
                                    //快速切换到STA模式
                                    LogUtils.d(FirmwareUpdate.this, "发送切换STA模式命令");
                                    radarProvide.switchStaMode();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }).start();
                            break;

                        case Values.SET_COLLECT_GATE_STATE:
                            //雷达已成功切换STA模式，回调升级成功
                            callbackStatus(STATUS_UPDATE_SUCCESS);
                            //升级成功，关闭检查线程
                            isRun = false;
                            break;
                    }
                } else if (!isSuccess(data) && Values.SEND_FIRMWARE_DATA.equals(tag)){
                    //如果在发送固件过程中出现失败，则执行重发
                    resendFirmware();
                } else {
                    //雷达返回升级失败！
                    callbackStatus(STATUS_UPDATE_EXCEPTION);
                }

                /*if (Values.UPDATE_COMMAND.equals(tag)){
                    if (hex2String(data).equals(hex2String(CommandList.FIRMWARE_ACK))
                            || hex2String(data).indexOf("0004CC00CC") != -1){
                        ackLoop++;
                        sendCommand();
                    }
                }else if (Values.SET_COLLECT_GATE_STATE.equals(tag)){
                    isRun = false;
                    callbackStatus(STATUS_UPDATE_SUCCESS);
                }*/
            }
        };
    }

    //发送命令控制逻辑
    private void sendCommand(){
//        if (ackLoop == 1){
//            tcpClient.updateWriteData(CommandList.getEraseCommand());
//        }

        if (ackLoop < fileSendCount){
            byte[] sendDataPack = CommandList.getSendDataPack(getCurrentFileByte());
            currentSendData = sendDataPack;
            tcpClient.updateWriteData(sendDataPack);
        }else {
            LogUtils.d(FirmwareUpdate.this, "固件升级成功命令");
            tcpClient.updateWriteData(CommandList.getUpdateSuccessCommand());
        }

        /*if (ackLoop == 1){
            tcpClient.updateWriteData(CommandList.getOpenCommand(fileLength));
        }else if (ackLoop >= 2 && ackLoop <= fileSendCount + 1){
            tcpClient.updateWriteData(CommandList.sendFileCommand(getCurrentFileByte()));
        }else if (ackLoop == fileSendCount + 2){
            tcpClient.updateWriteData(CommandList.getCloseCommand());
        }else if (ackLoop == fileSendCount + 3){
            tcpClient.updateWriteData(CommandList.getResetCommand());
        }else if (ackLoop == fileSendCount + 4){
            checkStaMode();
        }*/
    }

    /**
     * 固件重发
     * 如果在发送固件过程中，一条命令发送失败，则执行当前命令重发，达到一定次数则回调失败
     */
    private void resendFirmware(){
        if (currentResendCount > resendCount){
            //雷达返回升级失败！
            LogUtils.e(this, "发送固件数据失败");
            callbackStatus(STATUS_UPDATE_EXCEPTION);
            return;
        }

        LogUtils.w(this, "固件数据重发第" + (currentResendCount + 1) + "次");
        tcpClient.updateWriteData(currentSendData);
        currentResendCount++;
    }

    /**
     * 检查雷达是否没有回复消息
     */
    private void checkRadarMsg(){
        checkThread = new Thread(()->{
            while (isRun){
                try {
                    Thread.sleep(1000);
                    if (!isRun){
                        //防止在休眠的时候改变isRun的值
                        break;
                    }

                    currentTime++;
                    if (currentTime >= endTime){
                        LogUtils.w(this, "Radar回复消息超时！");
                        //雷达返回升级失败！
                        callbackStatus(STATUS_UPDATE_EXCEPTION);
                        break;
                    }
                } catch (InterruptedException e) {
                    checkThread.interrupt();
                    e.printStackTrace();
                }
            }
        });
        checkThread.start();
    }

    //切换到STA模式
    /*private void quicklySwitchStaMode(){
        new Thread(){
            @Override
            public void run() {
                try {
                    sleep(2000);
                    if (isRun){
                        radarProvide.switchStaMode();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }*/

    //加载升级文件
    private void loadUpdateFile(){
//        file = new File(BaseModuleInit.getAppContext().getCacheDir() + Values.FIRMWARE_PATH);
        file = new File(Values.FIRMWARE_PATH);
        LogUtils.d(this, "文件路径：" + file.toString());
        if (file.exists()){
            File[] fileList = file.listFiles();
            File updateFile = null;
            for (File f : fileList){
                if (f.getName().indexOf(FILE_TYPE) != -1){
                    String fileMd5 = MmkvHelper.getInstance().getMmkv().decodeString(Gobal.SAVE_KEY.BIN_FILE_MD5, "").toUpperCase();
                    if (getFileMD5(f).equals(fileMd5)){
                    //if (true){
                        updateFile = f;
                        fileLength = (int) f.length();
                        fileSendCount = fileLength / DATA_PACK_SIZE;
                        if (fileLength % DATA_PACK_SIZE != 0){
                            //文件长度没有整除，则次数+1
                            fileSendCount += 1;
                        }
                        isFileReady = true;
                        if (isTcpConnectReady){
                            callbackStatus(STATUS_READY);
                        }
                        LogUtils.d(this, "正在加载：" + f.getName() + "，大小：" + fileLength + "字节");
                        initTcp();
                        break;
                    }else {
                        LogUtils.d(this, "固件Md5值不一致");
                    }
                }
            }
            if (updateFile == null){
                //文件不存在
                callbackStatus(FILE_NOT_FOUND);
                LogUtils.d(this, "找不到" + FILE_TYPE + "文件！");
            }else {
                try {
                    inputStream = new FileInputStream(updateFile);
                } catch (IOException e) {
                    e.printStackTrace();
                    callbackStatus(FILE_READ_FAILED);
                    LogUtils.d(this, "文件读取失败！");
                }
            }
        } else {
            callbackStatus(FILE_NOT_FOUND);
            LogUtils.d(this, "文件路径不存在！");
        }
    }

    //获取当前文件上传的数据包
    private byte[] getCurrentFileByte(){
        byte[] result = null;
        try {
            byte[] dataPack = new byte[DATA_PACK_SIZE];
            if (inputStream.read(dataPack, 0, DATA_PACK_SIZE) != -1){
                if (sendCount == fileLength / DATA_PACK_SIZE){
                    //最后一次发送数据包
                    int arrSize = fileLength % DATA_PACK_SIZE;
                    byte[] temp = new byte[arrSize];
                    System.arraycopy(dataPack, 0, temp, 0, arrSize);
                    result = temp;
                }else {
                    result = dataPack;
                }
                sendCount++;
                countSendLength += result.length;
                //回调进度，返回百分比
                callbackProgress(countSendLength / (float) fileLength);
            } else {
                LogUtils.d(this, "文件读取完成！");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    //开始升级
    public void startUpdate(){
        if (tcpClient.isConnected()){
            new Thread(){
                @Override
                public void run() {
                    tcpClient.updateWriteData(CommandList.getStartUpdateCommand(getFileLength()));
                    //开始固件升级，启动检查线程
                    checkRadarMsg();
                }
            }.start();
        }else {
            callbackStatus(STATUS_DEVICE_UNCONNECTED);
        }
    }

    //关闭模块，释放资源
    public void close(){
        //升级完成，记得关闭文件流与其他统计变量，否则下次使用会出Bug
        try {
            if (inputStream != null){
                inputStream.close();
            }
            if(null != tcpClient){
                tcpClient.closeConnect();
                tcpClient = null;
            }
            if (null != checkThread){
                checkThread.interrupt();
            }

            callback = null;
            file = null;
            countSendLength = 0;
            fileSendCount = 0;
            ackLoop = 0;
            isRun = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取文件MD5值
     * @param file
     * @return
     */
    private String getFileMD5(File file) {
        MessageDigest messagedigest = null;
        try {
            messagedigest = MessageDigest.getInstance("MD5");
            FileInputStream in = new FileInputStream(file);
            FileChannel ch = in.getChannel();
            MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0,
                    file.length());
            messagedigest.update(byteBuffer);
            String md5 = ByteUtils.bytesToHexString(messagedigest.digest());
            return md5;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return "";
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return "";
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }

    private void callbackStatus(int state){
        if (null != callback){
            callback.status(state);
        }
    }

    private void callbackProgress(float progress){
        if (null != callback){
            callback.progress(progress);
        }
    }

    //获取文件总大小
    public int getFileLength() {
        return fileLength;
    }

    //获取已上传的文件长度
    public int getCountSendLength(){
        return (int) countSendLength;
    }

    //设置回调
    public void setCallback(UpdateStatusCallback callback){
        this.callback = callback;
    }

    /**
     * 回调接口
     */
    public interface UpdateStatusCallback{
        //状态回调
        void status(int statusCode);

        //当前进度回调
        void progress(float progress);
    }

    /**
     * 是否成功
     * @param data
     * @return
     */
    private boolean isSuccess(byte[] data){
        return Values.SET_SUCCESS.equals(hex2String(data));
    }

    private String hex2String(byte[] bytes){
        return ByteUtils.bytesToHexString(bytes);
    }

    private static void printHexString(String str, byte[] bytes){
        LogUtils.d(FirmwareUpdate.class, str + "------->" + ByteUtils.bytesToHexString(bytes));
    }
}
